#!/usr/bin/perl

use FindBin qw($RealBin);
use lib "$RealBin/pm" ;

use File::Basename ;
use Cwd ;
use Cwd 'abs_path';
use gte ;
use Digest::MD5 qw(md5_hex) ;
use trick_version ;
use html ;
use strict ;

my @exclude_paths ;
my @swig_exclude_paths ;
my @ext_lib_paths ;
my @files_to_process ;
my @ext_lib_files ;
my %md5s ;
my $verbose_build = exists($ENV{'TRICK_VERBOSE_BUILD'}) ;

sub get_paths {
    my @paths = split /:/ , $ENV{$_[0]} ;
    if (scalar @paths) {
        @paths = sort(@paths ) ;
        # trim whitespace
        @paths = map { s/(^\s+|\s+$)//g ; $_ } @paths ;
        # Remove empty elements. Sort forced all blank names to the front.
        while ( scalar @paths and not length @paths[0] ) {
            shift @paths ;
        }
        @paths = map { (-e $_) ? abs_path($_) : $_ } @paths ;
    }
    return @paths ;
}

sub read_files_to_process() {
    my @include_paths ;
    my $cc ;
    my @defines ;
    my ($version, $thread, $year) ;

    ($version, $thread) = get_trick_version() ;
    ($year) = $version =~ /^(\d+)/ ;
    ($cc = gte("TRICK_CC")) =~ s/\n// ;

    # add -I paths and #defines
    foreach my $value ( "TRICK_CFLAGS", "TRICK_CXXFLAGS" ) {
        push @include_paths, $ENV{$value} =~ /(-I\s*\S+)/g ;
        push @defines, $ENV{$value} =~ /(-D\S+)/g ;
    }
    push @include_paths , ("-I".$ENV{"TRICK_HOME"}."/include") ;
    push @include_paths , ("-I".$ENV{"TRICK_HOME"}."/include/trick/compat") ;
    push @include_paths , ("-I".$ENV{"TRICK_HOME"}."/trick_source" , "-I../include") ;
    push @defines , "-DTRICK_VER=$year" ;
    push @defines , "-DSWIG" ;
    push @defines , "-std=c++11" ;

    # get the list of header files from the compiler
    open FILE_LIST, "$cc -MM @include_paths @defines S_source.hh |" ;
    my $dir ;
    $dir = dirname(abs_path("S_source.hh")) ;
    my %files ;
    my %ext_libs ;
    while ( <FILE_LIST> ) {
        next if ( /^#/ or /^\s+\\/ ) ;
        outer:
        foreach my $word ( split ) {
            next if ( $word eq "\\" or $word =~ /o:/ ) ;

            # skip unsupported extensions
            next if not $word =~ /\.(H|h|hh|hxx|h++|hpp)$/ ;

            # get the absolute path
            if ( $word !~ /^\// and $dir ne "\/" ) {
                $word = "$dir/$word" ;
            }
            $word = abs_path(dirname($word)) . "/" . basename($word) ;

            # skip duplicate files
            next if (exists($md5s{$word})) ;

            # skip system headers that are missed by the compiler -MM flag
            next if ( $word =~ /^\/usr\/include/ ) ;

            # skip Trick headers
            my $trick_home = $ENV{'TRICK_HOME'} ;
            next if ( $word =~ /^\Q$trick_home\/include/ ) ;
            next if ( $word =~ /^\Q$trick_home\/trick_source/ ) ;

            # skip paths in TRICK_EXCLUDE
            foreach my $path ( @exclude_paths ) {
                if ( $word =~ /^\Q$path\E(.*)/ ) {
                    print "[95mSWIG Skip[39m  TRICK_EXCLUDE: [4m$path[24m$1\n" if $verbose_build ;
                    next outer ;
                }
                if ( abs_path($word) =~ /^\Q$path\E(.*)/ ) {
                    print "[95mSWIG Skip[39m  TRICK_EXCLUDE: [4m$path[24m$1\n" if $verbose_build ;
                    next outer ;
                }
            }

            # skip paths in TRICK_SWIG_EXCLUDE
            foreach my $path ( @swig_exclude_paths ) {
                if ( $word =~ /^\Q$path\E(.*)/ ) {
                    print "[95mSWIG Skip[39m  TRICK_SWIG_EXCLUDE: [4m$path[24m$1\n" if $verbose_build ;
                    next outer ;
                }
                if ( abs_path($word) =~ /^\Q$path\E(.*)/ ) {
                    print "[95mSWIG Skip[39m  TRICK_SWIG_EXCLUDE: [4m$path[24m$1\n" if $verbose_build ;
                    next outer ;
                }
            }

            # we'll be needing this later
            $md5s{$word} = md5_hex($word) ;

            # separate paths in TRICK_EXT_LIB_DIRS
            foreach my $path ( @ext_lib_paths ) {
                if ( $word =~ /^\Q$path\E(.*)/ ) {
                    print "[95mSWIG Skip[39m  TRICK_EXT_LIB_DIRS: [4m$path[24m$1\n" if $verbose_build ;
                    $ext_libs{$word} = 1 ;
                    next outer ;
                }
            }
            $files{$word} = 1 ;
        }
    }
    @ext_lib_files = sort keys %ext_libs ;
    @files_to_process = sort keys %files ;
}

sub write_makefile_swig_deps() {
    open DEPENDENCIES_FILE , ">build/Makefile_swig_deps" or return ;
    print DEPENDENCIES_FILE "build/Makefile_swig:" ;
    foreach my $file ( @files_to_process, @ext_lib_files ) {
        print DEPENDENCIES_FILE " \\\n    " . $file ;
    }
    close DEPENDENCIES_FILE ;
}

sub has_swig_no($) {
    my %trick_header = extract_trick_header( $_[0], do { local( @ARGV, $/ ) = $_[0] ; <> }, 0, 0 ) ;
    my $result = $trick_header{swig} =~ /^NO$/i ;
    print "[95mSWIG Skip[39m  SWIG: (NO): $_[0]\n" if $verbose_build and $result ;
    return $result ;
}

sub purge_swig_no_files() {
    @files_to_process = grep { !has_swig_no($_) } @files_to_process ;
    @ext_lib_files = grep { !has_swig_no($_) } @ext_lib_files ;
}

sub write_makefile_swig() {

    my ($n , $f , $k , $m);
    my %temp_hash ;
    my ($ii) ;
    my ($swig_sim_dir, $swig_src_dir) ;
    my (%py_module_map) ;
    my $s_source_md5 = md5_hex(abs_path("S_source.hh")) ;

    $swig_sim_dir = "trick" ;
    $swig_src_dir = "build" ;

    open MAKEFILE , ">build/Makefile_swig" or return ;
    open PY_LINK_LIST , ">build/py_link_list" or return ;
    print PY_LINK_LIST "build/init_swig_modules.o\n" ;
    print PY_LINK_LIST "build/top.o\n" ;

    print MAKEFILE "SWIG_CFLAGS := -I../include \${PYTHON_INCLUDES} -Wno-shadow -Wno-missing-field-initializers

ifeq (\$(IS_CC_CLANG), 1)
    SWIG_CFLAGS += -Wno-self-assign -Wno-sometimes-uninitialized -Wno-deprecated-register
else
    SWIG_CFLAGS += -Wno-unused-but-set-variable
endif

ifndef TRICK_VERBOSE_BUILD
    PRINT_SWIG          = \$(info \$(call COLOR,SWIGing)    \$<)
    PRINT_COMPILE_SWIG  = \$(info \$(call COLOR,Compiling)  \$<)
endif

# TRICK_FIXED_PYTHON ===========================================================

TRICK_FIXED_PYTHON = \\
    $swig_sim_dir/swig_double.py \\
    $swig_sim_dir/swig_int.py \\
    $swig_sim_dir/swig_ref.py \\
    $swig_sim_dir/shortcuts.py \\
    $swig_sim_dir/unit_test.py \\
    $swig_sim_dir/sim_services.py \\
    $swig_sim_dir/exception.py

\$(TRICK_FIXED_PYTHON): $swig_sim_dir/\% : \${TRICK_HOME}/share/trick/swig/\%
\t\@echo /bin/cp -f \$< \$@ >> \$(MAKE_OUT)
\t\$(ECHO_CMD)/bin/cp -f \$< \$@ 2>&1 | \$(TEE) -a \$(MAKE_OUT) ; exit \$\${PIPESTATUS[0]}

all: \$(TRICK_FIXED_PYTHON)

# SWIG_I =======================================================================

SWIG_I =" ;

    foreach my $file ( @files_to_process ) {
        (my $swig_file = $file) =~ s/\.[^.]*$/_py.i/ ;
        print MAKEFILE " \\\n    build$swig_file" ;
        $swig_file =~ s/i$/o/ ;
        print PY_LINK_LIST "build$swig_file\n" ;
    }

    print MAKEFILE "

define create_convert_swig_rule
build/%_py.i: /%.\$1
\t\@echo \${TRICK_HOME}/\$(LIBEXEC)/trick/convert_swig \${TRICK_CONVERT_SWIG_FLAGS} \$\$< >> \$(MAKE_OUT)
\t\$(ECHO_CMD)\${TRICK_HOME}/\$(LIBEXEC)/trick/convert_swig \${TRICK_CONVERT_SWIG_FLAGS} \$\$< 2>&1 | \$(TEE) -a \$(MAKE_OUT) ; exit \$\${PIPESTATUS[0]}
endef

EXTENSIONS := H h hh hxx h++ hpp
\$(foreach EXTENSION,\$(EXTENSIONS),\$(eval \$(call create_convert_swig_rule,\$(EXTENSION))))

build/top.i: build/CP_instances
\t\@echo \${TRICK_HOME}/\${LIBEXEC}/trick/create_top_dot_i >> \$(MAKE_OUT)
\t\${ECHO_CMD}\${TRICK_HOME}/\${LIBEXEC}/trick/create_top_dot_i 2>&1 | \$(TEE) -a \$(MAKE_OUT) ; exit \$\${PIPESTATUS[0]}

# SWIG_SRC =====================================================================

SWIG_SRC = \$(subst .i,.cpp,\$(SWIG_I)) $swig_src_dir/top.cpp

\$(SWIG_SRC) : %.cpp: %.i | \$(SWIG_I)
\t\$(PRINT_SWIG)
\t\@echo \$(SWIG) \$(TRICK_INCLUDE) \$(TRICK_DEFINES) \$(TRICK_VERSIONS) \$(SWIG_FLAGS) -c++ -python -includeall -ignoremissing -w201,303,325,362,389,401,451 -outdir trick -o \$@ \$< >> \$(MAKE_OUT)
\t\$(ECHO_CMD)\$(SWIG) \$(TRICK_INCLUDE) \$(TRICK_DEFINES) \$(TRICK_VERSIONS) \$(SWIG_FLAGS) -c++ -python -includeall -ignoremissing -w201,303,325,362,389,401,451 -outdir trick -o \$@ \$< 2>&1 | \$(TEE) -a \$(MAKE_OUT) ; exit \$\${PIPESTATUS[0]}

# SWIG_OBJECTS =================================================================

SWIG_OBJECTS = \$(subst .cpp,.o,\$(SWIG_SRC)) $swig_src_dir/init_swig_modules.o

\$(SWIG_OBJECTS): %.o: %.cpp
\t\$(PRINT_COMPILE_SWIG)
\t\@echo \$(TRICK_CPPC) \$(TRICK_CXXFLAGS) \$(TRICK_SYSTEM_CXXFLAGS) \$(SWIG_CFLAGS) -Wno-unused-parameter -c -o \$@ \$< >> \$(MAKE_OUT)
\t\$(ECHO_CMD)\$(TRICK_CPPC) \$(TRICK_CXXFLAGS) \$(TRICK_SYSTEM_CXXFLAGS) \$(SWIG_CFLAGS) -Wno-unused-parameter -c -o \$@ \$< 2>&1 | \$(TEE) -a \$(MAKE_OUT) ; exit \$\${PIPESTATUS[0]}

\$(S_MAIN): \$(SWIG_OBJECTS)

LINK_LISTS += \$(LD_FILELIST)build/py_link_list
" ;

    close MAKEFILE ;
    close PY_LINK_LIST ;

    open SWIGLIB , ">build/S_library_swig" or return ;
    foreach my $f ( @files_to_process ) {
        print SWIGLIB "$f\n" ;
    }
    close SWIGLIB ;

    open INITSWIGFILE , ">build/init_swig_modules.cpp" or return ;
    print INITSWIGFILE "#include <Python.h>\n" ;
    print INITSWIGFILE "#if PY_VERSION_HEX >= 0x03000000\n" ;
    print INITSWIGFILE "extern \"C\" {\n\n" ;

    foreach $f ( @files_to_process, @ext_lib_files ) {
        print INITSWIGFILE "PyObject * PyInit__m$md5s{$f}(void) ; /* $f */\n" ;
    }

    print INITSWIGFILE "PyObject * PyInit__sim_services(void) ;\n" ;
    print INITSWIGFILE "PyObject * PyInit__top(void) ;\n" ;
    print INITSWIGFILE "PyObject * PyInit__swig_double(void) ;\n" ;
    print INITSWIGFILE "PyObject * PyInit__swig_int(void) ;\n" ;
    print INITSWIGFILE "PyObject * PyInit__swig_ref(void) ;\n" ;

    print INITSWIGFILE "\nvoid init_swig_modules(void) {\n\n" ;
    foreach $f ( @files_to_process, @ext_lib_files ) {
        next if ( $f =~ /S_source.hh/ ) ;
        print INITSWIGFILE "    PyImport_AppendInittab(\"_m$md5s{$f}\", PyInit__m$md5s{$f}) ;\n" ;
    }
    print INITSWIGFILE "    PyImport_AppendInittab(\"_m${s_source_md5}\", PyInit__m${s_source_md5}) ;\n" ;
    print INITSWIGFILE "    PyImport_AppendInittab(\"_sim_services\", PyInit__sim_services) ;\n" ;
    print INITSWIGFILE "    PyImport_AppendInittab(\"_top\", PyInit__top) ;\n" ;
    print INITSWIGFILE "    PyImport_AppendInittab(\"_swig_double\", PyInit__swig_double) ;\n" ;
    print INITSWIGFILE "    PyImport_AppendInittab(\"_swig_int\", PyInit__swig_int) ;\n" ;
    print INITSWIGFILE "    PyImport_AppendInittab(\"_swig_ref\", PyInit__swig_ref) ;\n" ;
    print INITSWIGFILE "    return ;\n}\n\n}\n" ;
    print INITSWIGFILE "#else\n" ;

    print INITSWIGFILE "extern \"C\" {\n\n" ;

    foreach $f ( @files_to_process, @ext_lib_files ) {
        print INITSWIGFILE "void init_m$md5s{$f}(void) ; /* $f */\n" ;
    }

    print INITSWIGFILE "void init_sim_services(void) ;\n" ;
    print INITSWIGFILE "void init_top(void) ;\n" ;
    print INITSWIGFILE "void init_swig_double(void) ;\n" ;
    print INITSWIGFILE "void init_swig_int(void) ;\n" ;
    print INITSWIGFILE "void init_swig_ref(void) ;\n" ;

    print INITSWIGFILE "\nvoid init_swig_modules(void) {\n\n" ;
    foreach $f ( @files_to_process, @ext_lib_files) {
        next if ( $f =~ /S_source.hh/ ) ;
        print INITSWIGFILE "    init_m$md5s{$f}() ;\n" ;
    }
    print INITSWIGFILE "    init_m${s_source_md5}() ;\n" ;
    print INITSWIGFILE "    init_sim_services() ;\n" ;
    print INITSWIGFILE "    init_top() ;\n" ;
    print INITSWIGFILE "    init_swig_double() ;\n" ;
    print INITSWIGFILE "    init_swig_int() ;\n" ;
    print INITSWIGFILE "    init_swig_ref() ;\n" ;
    print INITSWIGFILE "    return ;\n}\n\n}\n" ;
    print INITSWIGFILE "#endif\n" ;
    close INITSWIGFILE ;

    if ( ! -e "trick") {
        mkdir "trick" ;
    }
    open INITFILE , ">trick/__init__.py" or return ;

    print INITFILE "from pkgutil import extend_path\n" ;
    print INITFILE "__path__ = extend_path(__path__, __name__)\n" ;
    print INITFILE "import sys\n" ;
    print INITFILE "import os\n" ;
    print INITFILE "sys.path.append(os.getcwd() + \"/trick\")\n" ;

    print INITFILE "\n" ;
    print INITFILE "import _sim_services\n" ;
    print INITFILE "from sim_services import *\n\n" ;

    print INITFILE "# create \"all_cvars\" to hold all global/static vars\n" ;
    print INITFILE "all_cvars = new_cvar_list()\n" ;
    print INITFILE "combine_cvars(all_cvars, cvar)\n" ;
    print INITFILE "cvar = None\n\n" ;

    foreach $f ( @files_to_process, @ext_lib_files ) {
        print INITFILE "# $f\n" ;
        print INITFILE "import _m$md5s{$f}\n" ;
        print INITFILE "from m$md5s{$f} import *\n" ;
        print INITFILE "combine_cvars(all_cvars, cvar)\n" ;
        print INITFILE "cvar = None\n\n" ;
    }

    print INITFILE "# S_source.hh\n" ;
    print INITFILE "import _m${s_source_md5}\n" ;
    print INITFILE "from m${s_source_md5} import *\n\n" ;
    print INITFILE "import _top\n" ;
    print INITFILE "import top\n\n" ;
    print INITFILE "import _swig_double\n" ;
    print INITFILE "import swig_double\n\n" ;
    print INITFILE "import _swig_int\n" ;
    print INITFILE "import swig_int\n\n" ;
    print INITFILE "import _swig_ref\n" ;
    print INITFILE "import swig_ref\n\n" ;
    print INITFILE "from shortcuts import *\n\n" ;
    print INITFILE "from exception import *\n\n" ;
    print INITFILE "cvar = all_cvars\n\n" ;
    close INITFILE ;

    return ;
}

@exclude_paths = get_paths( "TRICK_EXCLUDE" ) ;
@swig_exclude_paths = get_paths( "TRICK_SWIG_EXCLUDE" ) ;
@ext_lib_paths = get_paths( "TRICK_EXT_LIB_DIRS" ) ;
read_files_to_process() ;
write_makefile_swig_deps() ;

# Remove SWIG: (NO) files, but not before they're written to the dependency file.
# If SWIG: (NO) is removed, Makefile_swig needs to be regenerated.
purge_swig_no_files() ;
write_makefile_swig() ;
